package com.nchuoj.judger.controller;

import com.feign.config.enums.ExchangeNameEnum;
import com.feign.config.enums.RedisHead;
import com.feign.config.enums.RouteKeyEnum;
import com.feign.exceptions.CustomException;
import com.feign.feign.requests.QuestionFeign;
import com.feign.message.ExpiredQuestionMessage;
import com.feign.message.Question2JudgeMessage;
import com.feign.pojo.JudgeTestCase;
import com.feign.pojo.TestCaseCreate;
import com.feign.util.pojo.ShellRunData;
import com.nchuoj.judger.uitl.JavaRunShell;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.io.*;
import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * TODO
 *
 * @Description
 * @Author ZW
 * @Date 2022/12/28 17:46
 **/
@Slf4j
@Component
public class MessageConsumer {
    //标识服务器所对应的redis头
    private String localHead;
    private String questionPathHead;
    @Value("${file.uploadFolder}")
    private String systemTestCasePath;
    public RedisTemplate<String, Object> redisTemplate;

    public RabbitTemplate rabbitTemplate;
    public QuestionFeign questionFeign;
    JavaRunShell javaRunShell;

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate, QuestionFeign questionFeign, RabbitTemplate rabbitTemplate, JavaRunShell javaRunShell) {
        this.redisTemplate = redisTemplate;
        this.questionFeign = questionFeign;
        this.rabbitTemplate = rabbitTemplate;
        this.javaRunShell = javaRunShell;
        try {
            localHead = Inet4Address.getLocalHost().getHostAddress();
            questionPathHead = RedisHead.TITLE_EXPIRATION_TIME.getRedisHead() + localHead + ":";
            log.info("判题服务器分配的redis id头为: {}", localHead);
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
        redisTemplate.opsForValue().set(RedisHead.JUDGE_SERVER.getRedisHead() + localHead + ":", localHead);
    }

    /**
     * TODO 处理来自判题队列的消息 保障代码运行,并处理测试用例保存
     *
     * @param question2JudgeMessage 来自判题队列的消息
     */
    @RabbitListener(queues = "judgeQueue")
    public void mqTest(@Payload Question2JudgeMessage question2JudgeMessage) {
        log.info("收到题目:{}", question2JudgeMessage);
        //获取题目存储的用例位置
        String testCasePath = getTestCassPath(question2JudgeMessage.getQuestionId());
        List<JudgeTestCase> judgeTestCases = runCode(question2JudgeMessage.getCode(), testCasePath, question2JudgeMessage.getUserId(), question2JudgeMessage.getTestCaseList());
        question2JudgeMessage.setTestCaseList(judgeTestCases);
        rabbitTemplate.convertAndSend(ExchangeNameEnum.QUESTION_DIRECT_EXCHANGE.getName(), RouteKeyEnum.JUDGE_2_QUESTION.getName(), question2JudgeMessage);
    }

    /**
     * 运行代码 返回运行数据
     *
     * @param code              代码
     * @param testCasePath      测试用例路径
     * @param judgeTestCaseList 需要填充的测试后的数据
     * @return 运行数据
     */
    private List<JudgeTestCase> runCode(String code, String testCasePath, Integer uid, List<JudgeTestCase> judgeTestCaseList) {
        testCasePath = systemTestCasePath + testCasePath;
        String codePath = saveCodeReturnPath(code, testCasePath, uid);
        if (Boolean.FALSE.equals(Optional.ofNullable(codePath).isPresent())) {
            throw new CustomException("保存代码出错");
        }
        File file = new File(testCasePath);
        File[] files = file.listFiles();
//        System.out.println(Arrays.toString(files));
        //如果测试文件为空
        if (Boolean.FALSE.equals(Optional.ofNullable(files).isPresent()) || files.length == 0) {
            file.delete();
            throw new CustomException("测试文件丢失");
        }
        for (JudgeTestCase judgeTestCase : judgeTestCaseList) {
            codeRunGetTestCaseResult(codePath, testCasePath, judgeTestCase);
        }
        return judgeTestCaseList;
    }


    /**
     * 运行代码获取返回的但个测试用例数据
     * @param codePath 代码保存的位置
     * @param testCasePath 测试用例存储的文件夹位置 -- 绝对路径
     * @param judgeTestCase question获取的部分测试数据,是需要填充并反馈的数据
     */
    private void codeRunGetTestCaseResult(String codePath, String testCasePath, JudgeTestCase judgeTestCase) {
        String filepathHead = testCasePath + File.separator + judgeTestCase.getId();
        ShellRunData shellRunData = javaRunShell.runJavaCode(codePath, filepathHead + ".in");
        judgeTestCase.setMessage(shellRunData.getErrorOut());
        File file = new File(filepathHead + ".out");
        judgeTestCase.setFlag(compareTheResult(file,shellRunData.getStandardOut()));
        judgeTestCase.setMemory(shellRunData.getMemory());
        judgeTestCase.setRunTime(shellRunData.getRunTime());
//        return judgeTestCase;
    }

    private  boolean compareTheResult(File file,String data){
        String s = null;
        try {
            s = IOUtils.toString(new FileInputStream(file));
        } catch (IOException e) {
            file.delete();
            throw new CustomException("读取 .out 文件异常");
        }
        if (Optional.ofNullable(s).isPresent() && Optional.ofNullable(data).isPresent()) {
            if (data.equals(s)) {
                return true;
            }
        }
        return false;
    }
    private String saveCodeReturnPath(String code, String testCasePath, Integer uid) {
        String s = testCasePath + File.separator + UUID.randomUUID().toString() + uid + ".java";
        System.out.println(s);
        File file = new File(s);
        if (file.exists()) {
            file.delete();
            return null;
        }
        try {
            file.createNewFile();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
            fileOutputStream.write(code.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            file.delete();
            return null;
        }
        return s;
    }

    /**
     * 获取对应题目id 存储服务器相应的位置
     *
     * @param qid 题目的id
     * @return 题目的目录
     */
    private String getTestCassPath(Integer qid) {
        String testCaseSavePath = (String) redisTemplate.opsForValue().get(questionPathHead + qid);
        //题目已经失效
        if (testCaseSavePath == null) {
            testCaseSavePath = UUID.randomUUID().toString();
            Map<Object, List<TestCaseCreate>> testCase = questionFeign.getTestCase(qid);
            log.info(" map Data : {}", testCase);
            List<TestCaseCreate> testCaseCreates = testCase.get("" + qid);
            if (testCaseCreates == null || testCaseCreates.size() == 0) {
                log.info("测试用例获取失败");
                return null;
            }
            saveTestCaseList(testCaseCreates, testCaseSavePath);
            redisTemplate.opsForValue().set(questionPathHead + qid, testCaseSavePath, 7, TimeUnit.DAYS);
            rabbitTemplate.convertAndSend(ExchangeNameEnum.CREATE_TIME_FANOUT_EXCHANGE.getName(), "", new ExpiredQuestionMessage());
            return testCaseSavePath;
        } else {
            redisTemplate.opsForValue().set(questionPathHead + qid, testCaseSavePath, 7, TimeUnit.DAYS);
            return testCaseSavePath;
        }
    }


    /**
     * 保存一道题目的所有测试用例
     *
     * @param testCase         需要保存的测试用例集合
     * @param testCaseSavePath 需要保存该道题目的映射目录
     */
    private void saveTestCaseList(List<TestCaseCreate> testCase, String testCaseSavePath) {

        File file = new File(systemTestCasePath + testCaseSavePath);
        file.mkdirs();
        for (TestCaseCreate testCaseCreate : testCase) {
            String input = testCaseCreate.getInput();
            String output = testCaseCreate.getOutput();
            createFile(testCaseCreate.getId(), "in", testCaseCreate.getInput(), testCaseSavePath);
            createFile(testCaseCreate.getId(), "out", testCaseCreate.getOutput(), testCaseSavePath);
        }
    }

    private void createFile(Integer fileName, String fileType, String content, String mapToPath) {
        String path = systemTestCasePath + mapToPath + "/" + fileName + "." + fileType;
        log.info("newFilePath: {}", path);
        File file = new File(path);
        FileOutputStream fileOutputStream = null;
        try {
            file.createNewFile();
            fileOutputStream = new FileOutputStream(file);
            fileOutputStream.write(content.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}
